package com.tbit.uqbike.webmanager.business.machine_op_log.aspect;

import cn.hutool.core.util.ObjectUtil;
import com.tbit.common.constant.OperationPlatform;
import com.tbit.common.entity.MachineOpLog;
import com.tbit.common.utils.TraceUtil;
import com.tbit.uqbike.object.business.UserInfo;
import com.tbit.uqbike.object.pojo.Machine;
import com.tbit.uqbike.service.base.MachineService;
import com.tbit.uqbike.webmanager.business.machine_op_log.dao.log.MachineOpLogDao;
import com.tbit.uqbike.webmanager.controller.BaseController;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.spel.SpelCompilerMode;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import com.tbit.uqbike.webmanager.business.machine_op_log.annotation.InsertMachineOpLog;

import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * @author <a href="mailto:zheng.zebiao@tbit.com.cn">zb</a>
 * @since 2024/11/19
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class MachineOpLogAspect {
    final MachineService machineService;
    final MachineOpLogDao machineOpLogDao;

    @Around("@annotation(opLog)")
    public Object around(ProceedingJoinPoint joinPoint, InsertMachineOpLog opLog) throws Throwable {
        // 根据注解内spel表达式获取目标车辆
        Machine machine = getMachineFromOpLog(joinPoint, opLog);
        if (machine == null) {
            return joinPoint.proceed();
        }

        try {
            // 执行逻辑
            Object result = joinPoint.proceed();

            handleLog(joinPoint, opLog, result, null, machine);
            return result;
        } catch (Throwable e) {
            handleLog(joinPoint, opLog, null, e, machine);
            throw e;
        }
    }

//    @AfterReturning(pointcut = "@annotation(opLog)", returning = "result")
//    public void afterReturning(JoinPoint joinPoint, InsertMachineOpLog opLog, Object result) {
//        handleLog(joinPoint, opLog, result, null);
//    }
//
//    @AfterThrowing(pointcut = "@annotation(opLog)", throwing = "e")
//    public void afterThrowing(JoinPoint joinPoint, InsertMachineOpLog opLog, Exception e) {
//        handleLog(joinPoint, opLog, null, e);
//    }

    private void handleLog(JoinPoint joinPoint, InsertMachineOpLog opLog, Object result, Throwable e, Machine machine) {
        try {
            // 如果没有异常，且设置了不记录异常日志，则不记录日志
            if (e != null && !opLog.logWhenThrow()) {
                return;
            }

            // 获取操作人
            String name = null;
            String phone = null;
            try {
                UserInfo userInfo = BaseController.fetchUserInfo();
                name = userInfo.getName();
                phone = userInfo.getPhone();
            } catch (Exception ex) {
                log.error("[车辆操作日志] 获取操作人信息失败", ex);
            }

            MachineOpLog logDb = MachineOpLog.builder()
                    .accountId(machine.getAccountId())
                    .machineId(machine.getMachineId())
                    .machineNO(machine.getMachineNO())
                    .userCode(machine.getUserCode())
                    .operation(opLog.operation())
                    .opType(ObjectUtil.defaultIfEmpty(opLog.opType(), (String) null))
                    .opUser(name)
                    .opUserPhone(phone)
                    .opPlatform(OperationPlatform.MANAGER.code)
                    .result(e == null)
                    .opTime(LocalDateTime.now())
                    .detail(buildDetail())
                    .build();
            machineOpLogDao.insert(logDb);
        } finally {
            MachineOpLogDetailHolder.remove();
        }
    }

    /**
     * 组装日志详情
     */
    private String buildDetail() {
        List<String> detailList = MachineOpLogDetailHolder.get();
        String traceId = TraceUtil.getTraceIdByLocal();
        if (traceId != null) {
            detailList.add("TraceID: " + TraceUtil.getTraceIdByLocal());
        }
        return detailList.isEmpty() ? null : String.join("\n", detailList);
    }

    private Machine getMachineFromOpLog(JoinPoint joinPoint, InsertMachineOpLog opLog) {
        Integer machineId = opLog.machineId().isEmpty() ? null : getSpelValue(joinPoint, opLog.machineId(), Integer.class);
        String machineNO = opLog.machineNO().isEmpty() ? null : getSpelValue(joinPoint, opLog.machineNO(), String.class);
        String userCode = opLog.userCode().isEmpty() ? null : getSpelValue(joinPoint, opLog.userCode(), String.class);
        if (machineId == null && machineNO == null && userCode == null) {
            return null;
        }

        // 根据注解内spel表达式获取目标车辆
        Machine machine = null;
        if (machineId != null) {
            machine = machineService.getByMachineId(machineId);
        } else if (machineNO != null) {
            machine = machineService.getByMachineNO(machineNO);
        } else {
            machine = machineService.getByUserCode(userCode);
        }
        if (machine == null) {
            log.error("[车辆操作日志] 未找到目标车辆 machineId={} machineNO={} userCode={}", machineId, machineNO, userCode);
        }
        return machine;
    }

    private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser(
            new SpelParserConfiguration(SpelCompilerMode.MIXED, null)
    );
    private static final ConcurrentMap<String, SpelExpression> SPEL_CACHE = new ConcurrentHashMap<>();

    /**
     * 解析spel表达式，用于获取注解中的表达式返回值
     */
    private <T> T getSpelValue(JoinPoint joinPoint, String spel, Class<T> clazz) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        SpelExpression expression = SPEL_CACHE.computeIfAbsent(spel, k -> SPEL_PARSER.parseRaw(spel));
        StandardEvaluationContext ctx = new StandardEvaluationContext();
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            ctx.setVariable("p" + i, args[i]);
            ctx.setVariable(signature.getParameterNames()[i], args[i]);
        }
        return expression.getValue(ctx, clazz);
    }
}
